package com.googlecode.bip.servlet.filter;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.ws.rs.core.Response;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import com.googlecode.bip.entity.Roles;
import cx.ath.choisnet.lang.ByteArrayBuilder;
import cx.ath.choisnet.servlet.ChainingHttpServletResponseWrapper;
import com.googlecode.bip.util.RolesIdentifier;

/**
 * com.googlecode.bip.servlet.filter.ContentRewriterFilter
 *
 * @author Claude CHOISNET
 */
public class ContentRewriterFilter implements Filter
{
    private final static Logger _logger = Logger.getLogger( ContentRewriterFilter.class );

    private final Map<String,String> patternReplaceMap = new HashMap<String,String>();

    // TODO: cache uri with no modification ?
    // TODO: cache for charset ! key = charsetIn@charsetOut
    // CharSet(String),Map<Pattern(byte[]),Replace(byte[])
    //private final Map<String,Map<byte[],byte[]>> patternReplaceCacheCharsetMap = new HashMap<String,Map<byte[],byte[]>>();

    // TODO: cache for charset + full content (static uri)
    // CharSet(String),Map<Pattern(byte[]),ReplaceFullContent(byte[] OR File)
    //private final Map<String,Map<byte[],byte[]>> patternReplaceCacheResultMap = new HashMap<String,Map<byte[],byte[]>>();

    /* (non-Javadoc)
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(
            final ServletRequest    servletRequest,
            final ServletResponse   servletResponse,
            final FilterChain       filterChain
            )
        throws IOException, ServletException
    {

_logger.setLevel( Level.ALL );

        if( servletResponse instanceof HttpServletResponse ) {
            final HttpServletRequest request = HttpServletRequest.class.cast( servletRequest );


            /// ----------- TEST
            /// ----------- TEST
            /// ----------- TEST
            if( servletResponse instanceof HttpServletResponse ) {
                HttpServletResponse response = HttpServletResponse.class.cast( servletResponse );

                if( _logger.isDebugEnabled() ) {
                    _logger.debug( "Set no cache to HttpServletResponse for :" + request.getRequestURI() );
                    }

                response.setHeader("Cache-Control", "no-cache");
                response.setDateHeader("Expires", 0);
                response.setHeader("Pragma", "No-cache");
                }

                HttpSession session = request.getSession( false );
                if( session == null ) {
                    _logger.debug( "NO HttpSession" );
                    }
                else {
                    Enumeration<String> names = session.getAttributeNames();

                    _logger.debug( "HttpSession: " + session );

                    while( names.hasMoreElements() ) {
                        String n = names.nextElement();
                        Object v = session.getAttribute( n );

                        _logger.debug( "HttpSession attr:" + n + "=" + v );
                        }
                    }
            /// ----------- TEST
            /// ----------- TEST
            /// ----------- TEST




            if( _logger.isDebugEnabled() ) {
                _logger.debug( "request.getRequestURI() : " + request.getRequestURI() );
                }

            ChainingHttpServletResponseWrapper wrappedResponse
                = new ChainingHttpServletResponseWrapper(
                    HttpServletResponse.class.cast( servletResponse )
                    )
            {
                private PrintWriter writer = null;
                // real output for Servlet
                private ServletOutputStream sos = null;
                private ByteArrayBuilder    bab = new ByteArrayBuilder();

                @Override
                public ServletOutputStream getOutputStream() throws IOException
                {
                    if( writer != null ) {
                        throw new IllegalStateException(
                                "getWriter() has already been "
                                + "called for this response"
                                );
                        }
                    if( sos == null ) {
                        sos = createOutputStream();
                        }
                    return sos;
                  }

                public PrintWriter getWriter() throws IOException
                {
                    if( writer != null ) {
                        return (writer);
                        }

                    if( sos != null ) {
                        throw new IllegalStateException("getOutputStream() has already been called for this response");
                        }

                    sos = createOutputStream();

                    String charEnc = servletRequest.getCharacterEncoding();
                    // HttpServletResponse.getCharacterEncoding() shouldn't return null
                    // according the spec, so feel free to remove that "if"
                    if( charEnc != null ) {
                        writer = new PrintWriter(new OutputStreamWriter(sos, charEnc));
                        }
                    else {
                        writer = new PrintWriter(sos);
                        }

                    if( _logger.isDebugEnabled() ) {
                        _logger.debug( "getCharacterEncoding()=" + charEnc );
                        }

                    return writer;
                }

              @Override
              public void setBufferSize(int size)
              {
                  bab.ensureCapacity( size );
                  super.setBufferSize( size );
              }

              @Override
              public void flushBuffer() throws IOException
              {
                  if( sos != null ) {
                      sos.flush();
                      }
              }

              @Override
              public void setContentLength(int length) {}

              @Override
              public void finishResponse() throws IOException
              {
                  if( _logger.isDebugEnabled() ) {
                      _logger.debug( "finishResponse() " + sos);
                      }
                  if( sos != null ) {
                      sos.close();
                      sos = null;
                      }
              }

              private ServletOutputStream createOutputStream()
              {
                  return new ServletOutputStream()
                  {
                    private boolean isClosed = false;

                    @Override
                    public void write( int b ) throws IOException
                    {
                        if( isClosed ) {
                            throw new IOException("Cannot write to a closed output stream");
                            }
                        bab.append( (byte) b );
                    }
                    @Override
                    public void write( byte[] bytes ) throws IOException
                    {
                        if( isClosed ) {
                            throw new IOException("Cannot write to a closed output stream");
                            }
                        bab.append( bytes );
                    }
                    @Override
                    public void write( byte[] bytes, int offset, int len ) throws IOException
                    {
                        if( isClosed ) {
                            throw new IOException("Cannot write to a closed output stream");
                            }
                        bab.append( bytes, offset, len );
                    }
                    @Override
                    public void flush() throws IOException
                    {
                        if( isClosed ) {
                            throw new IOException("Cannot flush a closed output stream");
                            }
                        //logger.debug( "flush() called" );
                    }
                    @Override
                    public void close() throws IOException
                    {
                        if( isClosed ) {
                            throw new IOException("Cannot close a closed output stream");
                            }

                        isClosed = true;
                        super.close();

                        ByteArrayBuilder babRewrite = doReplace( request, servletResponse, bab );

                        byte[] dbytes = babRewrite.array();

                        servletResponse.getOutputStream().write( dbytes );
                    }
                  };
              }
            };

            if( _logger.isDebugEnabled() ) {
                _logger.debug( "call next filterChain.doFilter" );
                }

            try {
                filterChain.doFilter(servletRequest, wrappedResponse);
_logger.debug( "AFTER  filterChain.doFilter" );
                }
            finally {
                wrappedResponse.finishResponse();
                }
            }
        else {
            _logger.warn( "Can't manage: " + servletResponse );
            filterChain.doFilter(servletRequest, servletResponse);
            }

        if( _logger.isDebugEnabled() ) {
            _logger.debug( "end of filterChain.doFilter" );
            }
    }

    private ByteArrayBuilder doReplace(
            final HttpServletRequest    request,
            final ServletResponse       servletResponse,
            final ByteArrayBuilder      babSource
            )
        throws UnsupportedEncodingException
    {
        ByteArrayBuilder babRewrite = babSource;
        String charsetIn  = request.getCharacterEncoding();
        String charsetOut = servletResponse.getCharacterEncoding();

        if( charsetIn == null ) {
            charsetIn = Charset.defaultCharset().name();
            }
        if( charsetOut == null ) {
            charsetOut = Charset.defaultCharset().name();
            }

        ByteArrayBuilderReplace babr = new ByteArrayBuilderReplace( babRewrite );
        boolean                 babrChange = false;

        for( Entry<String,String> entry : patternReplaceMap.entrySet() ) {
            byte[] pattern = entry.getKey().getBytes( charsetIn );
            byte[] replace = entry.getValue().getBytes( charsetOut );

            if( babr.replaceAll( pattern, replace ) ) {
                babrChange = true;
                }
            }

        //
        // Replace strings:
        //  $[IS_BIP_USER]$   => true or false
        //  $[IS_BIP_AUTHOR]$ => true or false
        // ...
        //
        for( Roles role: RolesIdentifier.getRoles() ) {
            String patternStr = "$[IS_" + role.name() + "]$";
            String replaceStr = Boolean.toString(
                    RolesIdentifier.isUserInRole( request, role )
                    );

            if( _logger.isDebugEnabled() ) {
                _logger.debug( patternStr + " -> " + replaceStr );
                }

            byte[] pattern = patternStr.getBytes( charsetIn );
            byte[] replace = replaceStr.getBytes( charsetOut );

            if( babr.replaceAll( pattern, replace ) ) {
                babrChange = true;
                }
            }

        if( babrChange ) {
            babRewrite = babr.getByteArrayBuilder();

            if( servletResponse instanceof HttpServletResponse ) {
                HttpServletResponse response = HttpServletResponse.class.cast( servletResponse );

                if( _logger.isDebugEnabled() ) {
                    _logger.debug( "Set no cache to HttpServletResponse for :" + request.getRequestURI() );
                    }

                response.setHeader("Cache-Control", "no-cache");
                response.setDateHeader("Expires", 0);
                response.setHeader("Pragma", "No-cache");
                }
            else {
                _logger.warn( "servletResponse not HttpServletResponse : " + servletResponse.getClass() );
                }
            }
        else {
            if( _logger.isDebugEnabled() ) {
                _logger.debug( "No changes for :" + request.getRequestURI() );
                }
            }

        return babRewrite;
    }


    /* (non-Javadoc)
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init( FilterConfig filterConfig ) throws ServletException
    {
        if( _logger.isDebugEnabled() ) {
            _logger.debug( "ContentRewriterFilter loaded" );
            }

//        //TO DO: remove this (test only)
//        patternReplaceMap.put(
//                "><b>Up To /</b><",
//                "><b><i>Dossier parent</i> /</b><"
//                );
        //TODO: improve this ! (dynamic, config)
        patternReplaceMap.put(
                "$[JSON-RPC-URI]$",
                filterConfig.getServletContext().getContextPath() + "/JSON-RPC"
                );

    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy()
    {
        // TODO Auto-generated method stub
    }

}


/*
    public void doFilter2( ServletRequest request, ServletResponse response,
            FilterChain chain ) throws IOException, ServletException
    {
        boolean authorized = false;

        if( request instanceof HttpServletRequest ) {
            HttpSession session = ((HttpServletRequest)request)
                    .getSession( false );
            if( session != null ) {
                User user = (User)session.getAttribute( "user" );
                if( user != null )
                authorized = true;
            }
        }

        if( authorized ) {
            chain.doFilter( request, response );
            return;
       }
        else if( filterConfig != null ) {
            String login_page = filterConfig.getInitParameter( "login_page" );

            if( login_page != null && !"".equals( login_page ) ) {
                filterConfig.getServletContext()
                        .getRequestDispatcher( login_page ).

                        forward( request, response );
               return;
            }
        }

        throw new ServletException( "Unauthorized access, unable to forward to login page" );
    }



*/
